#include "Window.h"

///Copyright (c) 2009-2010 T. Klepzig 

map<HWND, Window*> Window::instances;
Window* Window::tmpInstance = 0;

Window::Window()
{
	hWnd = 0;
	hInstance = 0;
	wndProc = 0;
	screenDocking = true;
	screenDockingRange = 40;
}

Window::~Window()
{
	Close();
}


void Window::Create(HINSTANCE hInstance, LPCTSTR className, WndProc wndProc, DWORD exStyle, DWORD style, UINT classStyle, HBRUSH hbrBackground, HCURSOR hCursor, HICON hIcon, HWND hWndParent, HMENU hMenu, LPVOID lpParam)
{
	this->hInstance = hInstance;

	if (!hbrBackground)
		hbrBackground = GetSysColorBrush(COLOR_WINDOW);

	if (!hCursor)
		hCursor = LoadCursor(0,IDC_ARROW);

	this->wndProc = wndProc;

	WNDCLASSEX wndClass = {0};
	wndClass.cbSize = sizeof(wndClass);
	wndClass.lpfnWndProc = BaseWndProc;
	wndClass.hIcon = hIcon;
	wndClass.hInstance = hInstance;
	wndClass.hCursor = hCursor;
	wndClass.hbrBackground = hbrBackground;
	wndClass.style = classStyle;
	wndClass.lpszClassName = className;
	RegisterClassEx(&wndClass);

	tmpInstance = this;
	hWnd=CreateWindowEx(exStyle, className, 0, style, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, hWndParent, hMenu, hInstance, lpParam);
}

void Window::Close()
{
	if (hWnd)
	{
		DestroyWindow(hWnd);
		instances.erase(hWnd);
		hWnd = 0;
	}
}


void Window::Show()
{
	ShowWindow(hWnd, SW_SHOW);
}

void Window::ShowNoActivate()
{
	ShowWindow(hWnd, SW_SHOWNOACTIVATE);
}

void Window::Hide()
{
	ShowWindow(hWnd, SW_HIDE);
}

bool Window::IsVisible()
{
	return IsWindowVisible(hWnd);
}

HWND Window::GetHWnd()
{
	return hWnd;
}

bool Window::IsTopMost()
{
	return (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_TOPMOST) == WS_EX_TOPMOST;
}

void Window::SetTopMost(bool value)
{
	SetWindowPos(hWnd, value ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
}

void Window::SendToBottom()
{
	SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
}

void Window::BringOnTop()
{
	SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
}

tstring Window::GetText()
{
	TCHAR text[_MAX_PATH]={0};
	GetWindowText(hWnd, text, sizeof(text));

	tstring result(text);

	return result;
}

void Window::SetText(tstring text)
{
	SetWindowText(hWnd, text.c_str());
	InvalidateRgn(hWnd, 0, true);
}

void Window::SetIcon(HICON hIcon)
{
	SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
}

HICON Window::GetIcon()
{
	return (HICON)SendMessage(hWnd, WM_GETICON, ICON_BIG, 0);
}

void Window::SetBackgroundBrush(HBRUSH hBrush)
{
	SetClassLongPtr(hWnd, GCL_HBRBACKGROUND, (LONG)hBrush);
	InvalidateRgn(hWnd, 0, true);
}

HBRUSH Window::GetBackgroundBrush()
{
	return (HBRUSH)GetClassLongPtr(hWnd, GCL_HBRBACKGROUND);
}

void Window::SetCursor(HCURSOR hCursor)
{
	SetClassLongPtr(hWnd, GCL_HCURSOR, (LONG)hCursor);
	InvalidateRgn(hWnd, 0, true);
}

HCURSOR Window::GetCursor()
{
	return (HCURSOR)GetClassLongPtr(hWnd, GCL_HCURSOR);
}

RECT Window::GetBounds()
{
	RECT rect={0};
	GetWindowRect(hWnd, &rect);
	return rect;
}

void Window::SetBounds(LONG left, LONG top, LONG right, LONG bottom, bool force)
{
	RECT rect = {left, top, right, bottom};
	Window::SetBounds(rect, force);
}

void Window::SetBounds(RECT value, bool force)
{
	Window::SetLocation(value.left, value.top);
	Window::SetSize(RECTWIDTH(value), RECTHEIGHT(value), force);
}

SIZE Window::GetSize()
{
	SIZE size={0};
	RECT rect={0};

	GetWindowRect(hWnd, &rect);

	size.cx = RECTWIDTH(rect);
	size.cy = RECTHEIGHT(rect);

	return size;
}

void Window::SetSize(LONG cx, LONG cy, bool force)
{
	SIZE size = {cx, cy};
	Window::SetSize(size, force);
}

void Window::SetSize(SIZE value, bool force)
{
	SetWindowPos(hWnd, 0, 0, 0, value.cx, value.cy, force ? SWP_NOSENDCHANGING | SWP_NOMOVE | SWP_NOACTIVATE : SWP_NOMOVE | SWP_NOACTIVATE);
}

POINT Window::GetLocation()
{
	POINT point={0};
	RECT rect={0};

	GetWindowRect(hWnd, &rect);

	point.x = rect.left;
	point.y = rect.top;

	return point;
}

void Window::SetLocation(LONG x, LONG y)
{
	POINT point = {x, y};
	Window::SetLocation(point);
}

void Window::SetLocation(POINT value)
{
	SetWindowPos(hWnd, 0, value.x, value.y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
}

void Window::SetScreenDocking(bool value)
{
	screenDocking = value;
}

bool Window::GetScreenDocking()
{
	return screenDocking;
}

void Window::SetScreenDockingRange(int value)
{
	screenDockingRange = value;
}

int Window::GetScreenDockingRange()
{
	return screenDockingRange;
}

void Window::Refresh()
{
	InvalidateRgn(this->GetHWnd(), 0, true);
}

void Window::SetTag(LPVOID tag)
{
	this->tag = tag;
}

LPVOID Window::GetTag()
{
	return this->tag;
}


LRESULT CALLBACK Window::BaseWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (instances.find(hWnd) == instances.end() && tmpInstance)
	{
		instances[hWnd] = tmpInstance;
		tmpInstance = 0;
	}

	if (instances.find(hWnd) != instances.end())
	{
		if (instances[hWnd]->wndProc)
		{
			LRESULT ret = instances[hWnd]->wndProc(instances[hWnd], hWnd, uMsg, wParam, lParam);

			if (uMsg == WM_WINDOWPOSCHANGING && instances[hWnd]->screenDocking)
			{
				bool modified = false;
				WINDOWPOS* wp = (WINDOWPOS*)lParam;

				if ((wp->flags & SWP_NOMOVE) != SWP_NOMOVE)
				{
					HMONITOR monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTOPRIMARY);
					MONITORINFO mi;
					mi.cbSize = sizeof(MONITORINFO);
					GetMonitorInfo(monitor, &mi);

					int range = instances[hWnd]->screenDockingRange;

					if (wp->x < mi.rcWork.left + range && wp->x > mi.rcWork.left - range)
					{
						wp->x = mi.rcWork.left;
						modified = true;
					}
					else if ((wp->x + wp->cx) > mi.rcWork.right - range && (wp->x + wp->cx) < mi.rcWork.right + range)
					{
						wp->x = mi.rcWork.right - instances[hWnd]->GetSize().cx;
						modified = true;
					}
					if (wp->y < mi.rcWork.top + range && wp->y > mi.rcWork.top - range)
					{
						wp->y = mi.rcWork.top;
						modified = true;
					}
					else if ((wp->y + wp->cy) > mi.rcWork.bottom - range && (wp->y + wp->cy) < mi.rcWork.bottom + range)
					{
						wp->y = mi.rcWork.bottom - instances[hWnd]->GetSize().cy;
						modified = true;
					}

					if (modified)
					{
						lParam = (LPARAM)wp;
						return S_OK;
					}
				}
			}

			return ret;
		}
		else
			return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	else
	{
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
}
